#include "ramfstools.h"
#include <readline/readline.h>
#include <readline/history.h>
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>

enum cmd_type {
	 CMD_EXIT,
	 CMD_LIST,
	 CMD_MKDIR,
	 CMD_CHDIR,
	 CMD_RMDIR,
	 CMD_CHECK,
	 CMD_TOUCH,
	 CMD_REMOVE,
	 CMD_OPEN,
	 CMD_CLOSE,
	 CMD_READ,
	 CMD_READB,
	 CMD_READC,
	 CMD_POSBASE,
	 CMD_WRITE,
	 CMD_WRITEC, CMD_WRITEB,
	 CMD_WRITEX,
	 CMD_SEEKFWD,
	 CMD_SEEKBACK,
	 CMD_SEEKBEGIN,
	 CMD_SEEKEND,
	 CMD_COLUMNS,
	 CMD_IMPORT,
	 CMD_CUT,
	 CMD_PASTE,
	 CMD_UNKNOWN,
};

enum data_fmts {
	 FMT_CHAR,
	 FMT_BYTE,
};

const char* s_formats[] = {
	 "char",
	 "byte",
};

const int DIR_BASE_LEVELS = 256;

const int INITIAL_READ_LEN = 512;

const int INITIAL_POSITION_BASE = 10;

const int INITIAL_COLUMNS = 128;

const int BUFFER_SIZE = 4096;

char *prompt_end = ">";

struct level_info_t {
	 struct fs_node_t *node;
	 char *prompt;
};

struct path_t {
	 struct level_info_t *dirs;
	 int ndirs, cur_dir;
};

int init_path(struct path_t *path, int size);
void free_path(struct path_t *path);
int dup_path(struct path_t *src, struct path_t *dst);
int path_push(struct path_t *path, struct fs_node_t *dir, char *prompt);
void path_pop(struct path_t *path);
void split_option(char *src, char mark, char **first_end, char **sec_start);

int get_name(struct fs_node_t *f, int *namelen, char **name)
{
	 *namelen = fs_read(f, NULL, 0);
	 *name = malloc((*namelen + 1) * sizeof(char));
	 if (*name == NULL)
		  return 1;
	 if (fs_read(f, *name, *namelen) != *namelen)
	 {
		  free(*name);
		  *name = NULL;
		  return 2;
	 }
	 (*name)[*namelen] = '\0';
	 return 0;
}

// Function check
// Arguments
// f root directory to start check
// level current check depth
void
check(struct fs_node_t *f, int level)
{
	 struct fs_stat_t s;
	 int namelen;
	 char *name;

	 fs_seek(f, FS_SEEK_BEGIN, 0);
	 while (1)
	 {
		  if (fs_stat(f, &s) != 0) break;

		  if(get_name(f, &namelen, &name))
			   printf("ERROR: Failed to read file name.\n");
		  else {
			   int i;
			   for (i=0; i<level * 4; i++)
					putchar(' ');
			   printf("%s", name);
			   printf("[%c]\n", s.type == FILE_DIR ? 'D' : 'F');
			   if (s.type == FILE_DIR)
			   {
					for (i=0; i<level * 4; i++)
						 putchar(' ');
					printf("{\n");
					struct fs_node_t *ff = fs_open(f);
					check(ff, level+1);
					fs_close(ff);
					for (i=0; i<level * 4; i++)
						 putchar(' ');
					printf("}\n");
			   }
			   free(name);
		  }

		  if (fs_roll(f) != 0) break;
	 }

}

// Function cmd_to_id
// Arguments
// cmd command string
// Return value
// command id from enum cmd_type
enum cmd_type
cmd_to_id(char *cmd)
{
	 if (strcmp(cmd, "exit") == 0)
		  return CMD_EXIT;

	 if (strcmp(cmd, "ls") == 0)
		  return CMD_LIST;

	 if ((strcmp(cmd, "md") == 0) || (strcmp(cmd, "mkdir") == 0))
		  return CMD_MKDIR;

	 if ((strcmp(cmd, "cd") == 0) || (strcmp(cmd, "chdir") == 0))
		  return CMD_CHDIR;

	 if ((strcmp(cmd, "rd") == 0) || (strcmp(cmd, "rmdir") == 0))
		  return CMD_RMDIR;

	 if (strcmp(cmd, "check") == 0)
		  return CMD_CHECK;

	 if (strcmp(cmd, "touch") == 0)
		  return CMD_TOUCH;

	 if (strcmp(cmd, "rm") == 0)
		  return CMD_REMOVE;

	 if ((strcmp(cmd, "op") == 0) || (strcmp(cmd, "open") == 0))
		  return CMD_OPEN;

	 if (strcmp(cmd, "close") == 0)
		  return CMD_CLOSE;

	 if (strcmp(cmd, "readb") == 0)
		  return CMD_READB;

	 if (strcmp(cmd, "readc") == 0)
		  return CMD_READC;

	 if (strcmp(cmd, "posb") == 0)
		  return CMD_POSBASE;

	 if ((strcmp(cmd, "wc") == 0) || (strcmp(cmd, "writec") == 0))
		  return CMD_WRITEC;

	 if ((strcmp(cmd, "wb") == 0) || (strcmp(cmd, "writeb") == 0))
		  return CMD_WRITEB;

	 if (strcmp(cmd, "seekf") == 0)
		  return CMD_SEEKFWD;

	 if (strcmp(cmd, "seekb") == 0)
		  return CMD_SEEKBACK;

	 if (strcmp(cmd, "seeks") == 0)
		  return CMD_SEEKBEGIN;

	 if (strcmp(cmd, "seeke") == 0)
		  return CMD_SEEKEND;

	 if (strcmp(cmd, "read") == 0)
		  return CMD_READ;

	 if (strcmp(cmd, "write") == 0)
		  return CMD_WRITE;

	 if (strcmp(cmd, "cols") == 0)
		  return CMD_COLUMNS;

	 if (strcmp(cmd, "wx") == 0)
		  return CMD_WRITEX;

	 if ((strcmp(cmd, "imp") == 0) || (strcmp(cmd, "import") == 0))
		  return CMD_IMPORT;

	 if (strcmp(cmd, "cut") == 0)
		  return CMD_CUT;

	 if (strcmp(cmd, "paste") == 0)
		  return CMD_PASTE;

	 return CMD_UNKNOWN;
}

// Function enlarge_dirs
// doubles the size of the path
// Arguments
// path the path to the dir
// Return value
// 0 if successed, 1 if failed
int enlarge_dirs(struct path_t *path)
{
	 int nlevels = path->ndirs * 2;
	 struct level_info_t *ndirs = malloc(sizeof(struct level_info_t) * nlevels);
	 if (ndirs)
	 {
		  int i;
		  for (i=0; i<path->ndirs; i++)
			   ndirs[i] = path->dirs[i];
		  free(path->dirs);
		  path->dirs = ndirs;
		  path->ndirs = nlevels;
		  return 0;
	 }
	 return 1;
}

// Function find_obj
// Arguments
// work the working directory
// path the target object path
// type the found object type
// Return value
// NULL if not found, else an opened target
struct fs_node_t * find_obj(struct path_t *work, char *path, int *type, struct path_t *newpath)
{
	 printf("DEBUG: find_obj enter: path = %s\n", path);
	 struct path_t curdir;
	 struct fs_stat_t s;
	 if (init_path(&curdir, 0))
		  return NULL;
	 if (*path == '/')
	 {
		  curdir.dirs[0].node = fs_copynode(work->dirs[0].node);
		  while (*path == '/') path++;
	 } else {
		  if (dup_path(work, &curdir))
		  {
			   free_path(&curdir);
			   return NULL;
		  }
	 }
	 char *name = NULL;
	 do {
		  char *fe, *ss;
		  split_option(path, '/', &fe, &ss);
		  if (fe == NULL)
		  {
			   name = strdup(path);
			   break;
		  }
		  if (ss == NULL)
		  {
			   name = malloc((fe - path + 1) * sizeof(char));
			   if (name == NULL)
					return NULL;
			   strncpy(name, path, fe - path);
			   name[fe - path] = '\0';
			   break;
		  }
		  if ((fe - path == 2) && (strncmp(path, "..", 2) == 0))
		  {
			   if (curdir.cur_dir == 0)
					return NULL;
			   path_pop(&curdir);
			   path = ss;
		  } else if ((fe - path == 1) && (strncmp(path, ".", 1) == 0))
		  {
			   // Current directory: nothing to do
			   path = ss;
		  } else {
			   fs_seek(curdir.dirs[curdir.cur_dir].node, FS_SEEK_FIND, 0);
			   int res = fs_write(curdir.dirs[curdir.cur_dir].node, path, fe - path);
			   if (res == 0)
			   {
					fs_stat(curdir.dirs[curdir.cur_dir].node, &s);
					if (s.type != FILE_DIR)
						 return NULL;
					struct fs_node_t *dir = fs_open(curdir.dirs[curdir.cur_dir].node);
					if (dir == NULL)
						 return NULL;
					path_push(&curdir, dir, NULL);
					path = ss;
			   } else
					return NULL;
		  }
	 } while (true);

	 struct fs_node_t *dir;
	 int res;
	 if ((strcmp(name, ".") == 0) || (*name == '\0'))
	 {
		  // Current directory
		  if (type)
			   *type = FILE_DIR;
		  if (curdir.cur_dir == 0)
		  {
			   res = 1;
		  } else {
			   dir = fs_copynode(curdir.dirs[curdir.cur_dir - 1].node);
			   res = 0;
		  }
	 } else if ((strcmp(name, "..") == 0)) {
		  if (type)
			   *type = FILE_DIR;
		  if (curdir.cur_dir < 2)
		  {
			   res = 1;
		  } else {
			   dir = fs_copynode(curdir.dirs[curdir.cur_dir - 2].node);
			   res = 0;
		  }
	 } else {
		  dir = fs_copynode(curdir.dirs[curdir.cur_dir].node);
		  fs_seek(dir, FS_SEEK_FIND, 0);
		  res = fs_write(dir, name, strlen(name));
		  if ((res == 0) && (type))
		  {
			   fs_stat(dir, &s);
			   *type = s.type;
		  }
	 }

	 if (newpath)
	 {
		  free_path(newpath);
		  init_path(newpath, curdir.ndirs);
		  dup_path(&curdir, newpath);
	 }
	 free_path(&curdir);

	 return res == 0 ? dir : NULL;
}

// Function print_data
// Arguments
// data data to print
// len length of data to print
// fmt print format from data_fmts
// cols where to start a new line
void print_data(char *data, int len, enum data_fmts fmt, int cols)
{
	 int i;
	 int j = 0;
	 for (i=0; i<len; i++)
	 {
		  switch (fmt)
		  {
		  case FMT_CHAR:
			   printf("%c", *data);
			   if (*data == '\n')
					j = 0;
			   else
					j++;
			   data ++;
			   break;
		  case FMT_BYTE:
			   j++;
			   printf("%02u ", *data);
			   data ++;
			   break;
		  }
		  if (j == cols)
		  {
			   printf("\n");
			   j = 0;
		  }
	 }
	 if (j != 0)
		  printf("\n");
}

// Function parse_data
// Arguments
// data data to parse
// dptr parse result
// len length of the data
// fmt data format from enum data_fmts
// Return value
// 0 if parsed without error
int parse_data(char *data, char **dptr, int *len, enum data_fmts fmt)
{
	 switch (fmt) {
	 case FMT_CHAR:
		  *dptr = data;
		  *len = strlen(data);
		  break;
	 case FMT_BYTE:
	 {
		  *dptr = malloc(strlen(data) * sizeof(char));
		  char *dst = *dptr;
		  *len = 0;
		  int state = 0;
		  char ch = 0;
		  while (*data != '\0')
		  {
			   if ((*data >= '0') && (*data <= '9'))
			   {
					ch = ch * 10 + *data - '0';
					if (state == 0)
						 state = 1;
			   } else {
					if (state == 1)
					{
						 state = 0;
						 (*len)++;
						 *dst = ch;
						 ch = 0;
						 dst++;
					}
			   }
			   data++;
		  }
		  if (state == 1)
		  {
			   (*len)++;
			   *dst = ch;
		  }
		  break;
	 }
	 }
	 return 0;
}

// Function split_option
// Arguments
// src string of arguments to parse
// mark the delimiter of the arguments
// first_end the pointer to the first delimiter
// sec_start the pointer to the last one of the first group of delimiters
// Return value
// if first argument's length is 0 or there is no delimiter, first_end == NULL
// if second argument's length is 0, sec_start = NULL
void split_option(char *src, char mark, char **first_end, char **sec_start)
{
	 if (*src == mark)
	 {
		  *first_end = NULL;
		  return;
	 }
	 while ((*src != mark) && (*src != '\0')) src++;
	 if (*src == '\0')
	 {
		  *first_end = NULL;
		  return;
	 }
	 *first_end = src;
	 while (*src == mark) src++;
	 if (*src == '\0')
	 {
		  *sec_start = NULL;
		  return;
	 }
	 *sec_start = src;
}

int import_fs(struct fs_node_t *f, DIR *dir, char *path)
{
	 struct dirent *dp;
	 struct fs_stat_t s;
	 static char *buf = NULL;
	 if (buf == NULL)
		  buf = malloc(BUFFER_SIZE * sizeof(uint8_t));
	 if (buf == NULL)
		  return 1;

	 while ((dp = readdir(dir)) != NULL)
	 {
		  if ((strncmp(dp->d_name, ".", strlen(dp->d_name)) == 0) ||
			  (strncmp(dp->d_name, "..", strlen(dp->d_name)) == 0))
			   continue;
		  char *fpath = malloc(strlen(path) + 1 + strlen(dp->d_name) + 1);
		  if (fpath == NULL)
			   return 1;
		  strcpy(fpath, path);
		  strcat(fpath, "/");
		  strncat(fpath, dp->d_name, strlen(dp->d_name));
		  int fd;
		  DIR *sdir;
		  struct fs_node_t *ff;

		  switch (dp->d_type) {
		  case DT_REG:
			   fd = open(fpath, O_RDONLY);
			   if (fd == -1) break;
			   s.type = FILE_NORMAL;
			   fs_create(&s, dp->d_name, strlen(dp->d_name), f);
			   ff = fs_open(f);
			   if (ff == NULL) break;
			   int len;
			   while ((len = read(fd, buf, BUFFER_SIZE)) != 0)
					fs_write(ff, buf, len);
			   fs_close(ff);
			   break;
		  case DT_DIR:
			   sdir = opendir(fpath);
			   if (sdir == NULL) break;
			   s.type = FILE_DIR;
			   fs_create(&s, dp->d_name, strlen(dp->d_name), f);
			   ff = fs_open(f);
			   if (ff == NULL) break;
			   if (import_fs(ff, sdir, fpath))
					return 1;
			   fs_close(ff);
			   closedir(sdir);
			   break;
		  default:
			   printf("WARNING: Unknown file type omitted: %d\n", dp->d_type);
		  }

		  free(fpath);
	 }
	 return 0;
}

void renew_fs(struct path_t *path, struct fs_node_t **fd, struct fs_node_t **f, char **prompt)
{
	 if (*fd != NULL)
	 {
		  fs_close(*fd);
		  *fd = NULL;
	 }
	 int i;
	 for (i=path->cur_dir; i>=0; i--)
	 {
		  fs_close(path->dirs[i].node);
		  free(path->dirs[i].prompt);
	 }
	 *prompt = malloc(2 * sizeof(char));
	 sprintf(*prompt, ">");
	 *f = open_root();
	 path->cur_dir = 0;
	 path->dirs[0].node = *f;
	 path->dirs[0].prompt = *prompt;
}

int get_base(char *src, char **base, char **name)
{
	 int i;
	 int len = strlen(src);
	 for (i = len - 1; i >= 0; i--)
	 {
		  if (src[i] == '/')
		  {
			   if (i == len - 1)
					return 1;
			   *base = malloc((i+2) * sizeof(char));
			   if (*base == NULL)
					return 1;
			   *name = malloc((len - i) * sizeof(char));
			   if (*name == NULL)
			   {
					free(*base);
					return 1;
			   }
			   strncpy(*base, src, i+1);
			   (*base)[i+1] = '\0';
			   strcpy(*name, src + i + 1);
			   printf("DEBUG: get_base 1st exit: base = %s, name = %s\n", *base, *name);
			   return 0;
		  }
	 }
	 *base = malloc(2 * sizeof(char));
	 if (*base == NULL)
		  return 1;
	 *name = malloc((len+1) * sizeof(char));
	 if (*name == NULL)
	 {
		  free(*base);
		  return 1;
	 }
	 strcpy(*base, ".");
	 strcpy(*name, src);
	 printf("DEBUG: get_base 2st exit: base = %s, name = %s\n", *base, *name);
	 return 0;
}

int init_path(struct path_t *path, int size)
{
	 if (size == 0) size = DIR_BASE_LEVELS;
	 path->ndirs = size;
	 path->cur_dir = 0;
	 path->dirs = malloc(sizeof(struct level_info_t) * path->ndirs);
	 if (path->dirs == NULL)
		  return 1;
	 path->dirs[0].node = NULL;
	 path->dirs[0].prompt = NULL;
	 return 0;
}

void free_path(struct path_t *path)
{
	 if (path == NULL) return;
	 int i;
	 for (i=path->cur_dir; i>=0; i--)
	 {
		  if (path->dirs[i].node)
			   fs_close(path->dirs[i].node);
		  if (path->dirs[i].prompt)
			   free(path->dirs[i].prompt);
	 }
	 free(path->dirs);
}

int path_push(struct path_t *path, struct fs_node_t *dir, char *prompt)
{
	 path->cur_dir++;
	 if (path->cur_dir == path->ndirs)
		  if (enlarge_dirs(path))
		  {
			   path->cur_dir--;
			   return 1;
		  }
	 path->dirs[path->cur_dir].node = dir;
	 path->dirs[path->cur_dir].prompt = prompt;
	 return 0;
}

void path_pop(struct path_t *path)
{
	 if (path->cur_dir == 0) return;
	 fs_close(path->dirs[path->cur_dir].node);
	 if (path->dirs[path->cur_dir].prompt) free(path->dirs[path->cur_dir].prompt);
	 path->cur_dir--;
}

int dup_path(struct path_t *src, struct path_t *dst)
{
	 if (dst->ndirs <= src->cur_dir)
	 {
		  free_path(dst);
		  if (init_path(dst, src->ndirs))
			   return 1;
	 }
	 dst->cur_dir = src->cur_dir;
	 int i;
	 for (i=0; i<=src->cur_dir; i++)
	 {
		  dst->dirs[i].node = fs_copynode(src->dirs[i].node);
		  dst->dirs[i].prompt = NULL;
	 }
	 return 0;
}

int render_prompt(struct path_t *path)
{
	 int failed = 0;
	 int failpos = 0;
	 int endlen = strlen(prompt_end);
	 if (path->cur_dir < 0)
		  return 0;
	 path->dirs[0].prompt = malloc(endlen + 1);
	 strcpy(path->dirs[0].prompt, prompt_end);
	 int i;
	 int llen = endlen;
	 char *lprompt, *nprompt;
	 for (i = 1; i <= path->cur_dir; i++)
	 {
		  lprompt = path->dirs[i-1].prompt;
		  int namelen;
		  char *name;
		  if ((get_name(path->dirs[i-1].node, &namelen, &name)) || 
			  ((nprompt = malloc(llen + 2 + namelen)) == NULL))
		  {
			   failed = 1;
			   failpos = i;
			   break;
		  }
		  strcpy(nprompt, lprompt);
		  strcpy(nprompt + llen - endlen, "/");
		  strcat(nprompt, name);
		  strcat(nprompt, prompt_end);
		  if (path->dirs[i].prompt)
			   free(path->dirs[i].prompt);
		  path->dirs[i].prompt = nprompt;
		  llen += 1 + namelen;
	 }
	 if (failed)
	 {
		  int j;
		  for (j=0; j<failpos; j++)
		  {
			   free(path->dirs[j].prompt);
			   path->dirs[j].prompt = NULL;
			   return 1;
		  }
	 }

	 return 0;
}

int is_root(struct path_t *path, char *str)
{
	 printf("DEBUG: is_root enter: path %s\n", str);
	 int level = 0;
	 int state = 0;
	 // 0: in / 1: in name
	 char *name = NULL;
	 if (*str != '/')
	 {
		  level = path->cur_dir;
		  name = str;
	 } else str++;
	 while (true)
	 {
		  if ((*str == '/') || (*str == '\0'))
		  {
			   if (state == 1)
			   {
					if ((strncmp(name, "..", 2) == 0) && (str - name == 2))
					{
						 level--;
					} else if ((strncmp(name, ".", 1) == 0) && (str - name == 1))
					{
						 // Current directory
					} else level++;
					state = 0;
			   }
			   if (*str == '\0') break;
		  } else {
			   if (state == 0)
			   {
					state = 1;
					name = str;
			   }
		  }
		  str++;
	 }
	 printf("DEBUG: is_root level: %d\n", level);
	 return level == 0 ? 1 : 0;
}

int
main()
{
	 init_ramfs_driver();
	 init_fs();

	 struct fs_node_t *f;
	 f = open_root();

	 struct fs_node_t *fd = NULL;
	 int last_readlen = INITIAL_READ_LEN;
	 int pos_base = INITIAL_POSITION_BASE;
	 int last_fmt = FMT_CHAR;
	 int cols = INITIAL_COLUMNS;

	 struct fs_node_t *fa = NULL;
	 struct fs_node_t *fb = NULL;

	 char *cmd;

	 char *prompt;
	 char *clipboard = NULL;

	 prompt = malloc(2 * sizeof(char));
	 sprintf(prompt, ">");

	 struct path_t workdir;
	 init_path(&workdir, 0);
	 workdir.dirs[0].node = f;
	 workdir.dirs[0].prompt = prompt;

	 while ((cmd = readline(prompt)))
	 {
		  // cmd not null
		  if (*cmd)
			   add_history(cmd);
		  char *dpart = strstr(cmd, " ");
		  while (dpart && *dpart == ' ')
		  {
			   *dpart = 0;
			   dpart++;
		  }
		  if (dpart && *dpart == 0)
			   dpart = 0;
		  int id = cmd_to_id(cmd);
		  struct fs_stat_t s;
		  int t;
		  int n;
		  int fmt;
		  switch(id)
		  {
		  case CMD_EXIT:
			   printf("Bye!\n");
			   dump_root();
			   return 0;
		  case CMD_LIST:
			   //				printf("Dir List:\n");
			   fs_seek(f, FS_SEEK_BEGIN, 0);
			   do {
					if (fs_stat(f, &s) != 0)
						 break;
					int namelen;
					char *name;
					if (get_name(f, &namelen, &name))
						 printf("ERROR: Failed to read file name.\n");
					else {
						 printf("%s\t%d\t%s\n", s.type == FILE_DIR ? "d" : "-", s.size, name);
						 free(name);
					}
			   } while (fs_roll(f) == 0);
			   break;
		  case CMD_CHDIR:
		  {
			   struct path_t newpath;
			   init_path(&newpath, 0);
			   //					printf("Changing Directory.\n");
			   if (dpart == 0)
			   {
					// cd without param
					break;
			   }
			   if (strcmp(dpart, "..") == 0)
			   {
					// back one level
					if (workdir.cur_dir == 0)
					{
						 printf("ERROR: We are already in the root directory.\n");
					} else {
						 fs_close(f);
						 free(prompt);
						 workdir.cur_dir--;
						 f = workdir.dirs[workdir.cur_dir].node;
						 prompt = workdir.dirs[workdir.cur_dir].prompt;
					}
					break;
			   }
			   if (is_root(&workdir, dpart))
			   {
					while (workdir.cur_dir > 0) path_pop(&workdir);
					prompt = workdir.dirs[0].prompt;
					f = workdir.dirs[0].node;
			   } else if ((fa = find_obj(&workdir, dpart, &t, &newpath)))
			   {
					if (t == FILE_DIR)
					{
						 struct fs_node_t *nf = fs_open(fa);
						 if (nf)
						 {
							  free_path(&workdir);
							  workdir = newpath;
							  path_push(&workdir, nf, NULL);
							  render_prompt(&workdir);
							  prompt = workdir.dirs[workdir.cur_dir].prompt;
							  f = workdir.dirs[workdir.cur_dir].node;
							  printf("Changed to %s\n", dpart);
						 } else {
							  printf("ERROR: Cannot open target directory.\n");
							  free_path(&newpath);
						 }
					} else {
						 printf("ERROR: Not a directory.\n");
						 free_path(&newpath);
					}
			   } else {
					printf("ERROR: Directory not found.\n");
			   }
			   break;
		  }
		  case CMD_CHECK:
			   check(workdir.dirs[0].node, 0);
			   break;
		  case CMD_MKDIR:
		  case CMD_TOUCH:
			   if (dpart == 0)
			   {
					printf("ERROR: Touch operation must have an argument.\n");
					break;
			   }
			   if (find_obj(&workdir, dpart, NULL, NULL))
			   {
					printf("ERROR: File or directory already exists.\n");
					break;
			   }
			   if (id == CMD_TOUCH)
					s.type = FILE_NORMAL;
			   else if (id == CMD_MKDIR)
					s.type = FILE_DIR;
			   char *base;
			   char *name;
			   if (get_base(dpart, &base, &name))
			   {
					printf("ERROR: Failed to parse argument.\n");
			   } else {
					if ((strcmp(name, "..") == 0) || (strcmp(name, ".") == 0))
					{
						 printf("ERROR: Invalid %s name.\n", id == CMD_MKDIR ? "directory" : "file");
						 break;
					}
					if (is_root(&workdir, base))
					{
						 if(fs_create(&s, name, strlen(name), workdir.dirs[0].node))
						 {
							  printf("ERROR: Creation failed.\n");
						 }
					} else if ((fa = find_obj(&workdir, base, &t, NULL)) == NULL)
					{
						 printf("ERROR: Base directory not found.\n");
					} else {
						 if ((t != FILE_DIR) || ((fb = fs_open(fa)) == NULL))
						 {
							  printf("ERROR: Cannot open base directory.\n");
						 } else {
							  if (fs_create(&s, name, strlen(name), fb))
							  {
								   printf("ERROR: Creation failed.\n");
							  }
							  fs_close(fb);
						 }
						 fs_close(fa);
					}
					free(base);
					free(name);
			   }
			   break;
		  case CMD_REMOVE:
		  case CMD_RMDIR:
			   if (dpart == 0)
			   {
					printf("ERROR: Please specify %s to remove.\n", 
						   id == CMD_REMOVE ? "file" : "directory");
					break;
			   }
			   if ((fa = find_obj(&workdir, dpart, &t, NULL)))
			   {
					if ((id == CMD_REMOVE) && (t != FILE_NORMAL))
					{
						 printf("ERROR: Not a file.\n");
						 fs_close(fa);
						 break;
					}
					if ((id == CMD_RMDIR) && (t != FILE_DIR))
					{
						 printf("ERROR: Not a directory.\n");
						 fs_close(fa);
						 break;
					}
					if (fs_remove(fa))
					{
						 printf("ERROR: Remove failed.\n");
					} else
					{
						 printf("Removed %s.\n", dpart);
					}
					fs_close(fa);
			   } else {
					printf("ERROR: %s not found.\n", 
						   id == CMD_REMOVE ? "File" : "Directory");
			   }
			   break;
		  case CMD_OPEN:
			   if (dpart == 0)
			   {
					printf("ERROR: Please specify the file to open.\n");
					break;
			   }
			   if (fd)
			   {
					printf("WARNING: Closing opened file.\n");
					fs_close(fd);
					fd = NULL;
			   }
			   if ((fa = find_obj(&workdir, dpart, &t, NULL)))
			   {
					if (t != FILE_NORMAL)
					{
						 printf("ERROR: Please specify a file.\n");
						 break;
					}
					if ((fd = fs_open(fa)) == NULL)
					{
						 printf("ERROR: Open failed.\n");
					}
					fs_close(fa);
			   } else {
					printf("ERROR: File not found.\n");
			   }
			   break;
		  case CMD_CLOSE:
			   if (fd)
			   {
					if(fs_close(fd))
						 printf("ERROR: Close failed.\n");
					fd = NULL;
			   } else {
					printf("ERROR: No file opened.\n");
			   }
			   break;
		  case CMD_READ:
		  case CMD_READB:
		  case CMD_READC:
			   if (fd == NULL)
			   {
					printf("ERROR: No file opened.\n");
					break;
			   }
			   int readlen;
			   char *endptr;
			   if (dpart == NULL)
			   {
					readlen = last_readlen;
			   } else {
					readlen = strtol(dpart, &endptr, pos_base);
					if (*endptr != '\0')
					{
						 printf("ERROR: Cannot process the length argument.\n");
						 break;
					}
			   }
			   char *data = malloc(readlen * sizeof(char));
			   if (data == NULL)
			   {
					printf("ERROR: Out of memory.\n");
					break;
			   }
			   n = fs_read(fd, data, readlen);
			   switch (id) {
			   case CMD_READB:
					fmt = FMT_BYTE;
					break;
			   case CMD_READC:
					fmt = FMT_CHAR;
					break;
			   case CMD_READ:
					fmt = last_fmt;
					printf("Using last format %s\n", s_formats[fmt]);
					break;
			   }
			   last_fmt = fmt;
			   printf("Read %d byte(s).\n", n);
			   print_data(data, n, fmt, cols);
			   break;
		  case CMD_POSBASE:
			   if (dpart)
			   {
					char *endptr;
					int nposb = strtol(dpart, &endptr, 10);
					if (*endptr != '\0')
					{
						 printf("ERROR: Cannot process the base argument.\n");
						 break;
					} else {
						 if ((nposb > 36) || (nposb < 2))
						 {
							  printf("ERROR: Invalid base.\n");
							  break;
						 }
						 pos_base = nposb;
					}
			   } else
					pos_base = 10;
			   printf("Current position base: %d\n", pos_base);
			   break;
		  case CMD_WRITE:
		  case CMD_WRITEC:
		  case CMD_WRITEB:
		  case CMD_WRITEX:
		  {
			   if (dpart == NULL)
			   {
					printf("ERROR: Please specify content.\n");
					break;
			   }
			   if (fd == NULL)
			   {
					printf("ERROR: No file opened.\n");
					break;
			   }
			   char *dptr;
			   switch (id) {
			   case CMD_WRITEB:
					fmt = FMT_BYTE;
					break;
			   case CMD_WRITEC:
					fmt = FMT_CHAR;
					break;
			   case CMD_WRITE:
			   case CMD_WRITEX:
					fmt = last_fmt;
					printf("Using last format %s\n", s_formats[fmt]);
					break;
			   }
			   last_fmt = fmt;
			   int repcount = 1;
			   char *dstart = dpart;
			   if (id == CMD_WRITEX)
			   {
					char *firstend, *secstart;
					split_option(dpart, ' ', &firstend, &secstart);
					if ((firstend == NULL) || (secstart == NULL))
					{
						 printf("ERROR: Not enough arguments.\n");
						 break;
					}
					*firstend = '\0';
					repcount = strtol(dpart, &endptr, 10);
					if (*endptr != '\0')
					{
						 printf("ERROR: Cannot parse argument.\n");
						 break;
					}
					dstart = secstart;
			   }
			   parse_data(dstart, &dptr, &n, fmt);
			   int res = 0;
			   int i;
			   for (i=0; i<repcount; i++)
					res += fs_write(fd, dptr, n);
			   printf("%d byte(s) written.\n", res);
			   if (fmt != FMT_CHAR)
					free(dptr);
			   break;
		  }
		  case CMD_SEEKFWD:
		  case CMD_SEEKBACK:
		  case CMD_SEEKBEGIN:
		  case CMD_SEEKEND:
		  {
			   if (fd == NULL)
			   {
					printf("ERROR: No file opened.\n");
					break;
			   }
			   int npos;
			   if (dpart)
			   {
					char *endptr;
					npos = strtol(dpart, &endptr, pos_base);
					if (*endptr != '\0')
					{
						 printf("ERROR: Cannot process the position.\n");
						 break;
					}
			   } else
					npos = 0;
			   int dir;
			   switch (id) {
			   case CMD_SEEKFWD:
					dir = FS_SEEK_FORWARD;
					break;
			   case CMD_SEEKBACK:
					dir = FS_SEEK_BACKWARD;
					break;
			   case CMD_SEEKBEGIN:
					dir = FS_SEEK_BEGIN;
					break;
			   case CMD_SEEKEND:
					dir = FS_SEEK_END;
					break;
			   }
			   int res = fs_seek(fd, dir, npos);
			   printf("Effective position: %d\n", res);
			   break;
		  }
		  case CMD_COLUMNS:
		  {
			   if (dpart)
			   {
					char *endptr;
					int ncols = strtol(dpart, &endptr, 10);
					if (*endptr != '\0')
						 printf("ERROR: Cannot process the argument.\n");
					else
						 cols = ncols;
			   } else
					cols = INITIAL_COLUMNS;
			   printf("New columns: %d\n", cols);
			   break;
		  }
		  case CMD_IMPORT:
			   renew_fs(&workdir, &fd, &f, &prompt);
			   if (dpart == NULL)
					break;
			   DIR *rdir = opendir(dpart);
			   if (rdir == NULL)
			   {
					printf("ERROR: Failed to open root directory.\n");
					break;
			   }
			   import_fs(f, rdir, dpart);
			   closedir(rdir);
			   break;
		  case CMD_CUT:
			   if (dpart == NULL)
			   {
					printf("ERROR: Please specify file to cut.\n");
					break;
			   }
			   if (clipboard)
					free(clipboard);
			   clipboard = strdup(dpart);
			   break;
		  case CMD_PASTE:
		  {
			   if (dpart == NULL)
			   {
					printf("ERROR: Please specify target.\n");
					break;
			   }
			   if (clipboard == NULL)
			   {
					printf("ERROR: Please cut or paste first.\n");
					break;
			   }
			   if (is_root(&workdir, clipboard))
			   {
					printf("ERROR: Cannot move root directory.\n");
					break;
			   }
			   if (is_root(&workdir, dpart))
			   {
					printf("ERROR: Cannot move root directory.\n");
					break;
			   }
			   fa = find_obj(&workdir, clipboard, &t, NULL);
			   if (fa == NULL)
			   {
					printf("ERROR: Cannot find source object.\n");
					break;
			   }
			   fb = find_obj(&workdir, dpart, &t, NULL);
			   char *base = NULL, *name = NULL;
			   struct fs_node_t *fc = NULL;
			   struct fs_node_t *fd = NULL;
			   if (fb == NULL)
			   {
					if (get_base(dpart, &base, &name))
					{
						 printf("ERROR: Cannot parse destination path.\n");
						 goto error_paste_1;
					}
					if (is_root(&workdir, base))
					{
						 fc = fs_dup(workdir.dirs[0].node);
					} else {
						 fd = find_obj(&workdir, base, &t, NULL);
						 if ((fd == NULL)  || (t != FILE_DIR))
						 {
							  printf("ERROR: Cannot open destination directory.\n");
							  goto error_paste_1;
						 }
						 fc = fs_open(fd);
						 if (fc == NULL)
						 {
							  printf("ERROR: Cannot open destination directory.\n");
							  goto error_paste_1;
						 }
					}
					if (fs_create(&s, name, strlen(name), fc))
					{
						 printf("ERROR: Cannot create destination file.\n");
						 goto error_paste_1;
					}
					fb = find_obj(&workdir, dpart, &t, NULL);
					if (fb == NULL)
					{
						 printf("ERROR: Unknown failure after creation of the destination file.\n");
						 goto error_paste_1;
					}
			   }
			   if (fs_move(fa, fb))
			   {
					printf("ERROR: Move failed.\n");
			   }
		  error_paste_1:
			   if (base) free(base);
			   if (name) free(name);
			   if (fa) fs_close(fa);
			   if (fb) fs_close(fb);
			   if (fc) fs_close(fc);
			   if (fd) fs_close(fd);
			   break;
		  }
		  default:
			   printf("ERROR: Unknown command.\n");
		  }
	 }
	 free(cmd);
	 return 0;
}
